package com.zycfc.zsf.boot.util.bean;

import java.util.concurrent.*;
import net.sf.cglib.beans.*;
import java.io.*;
import java.lang.reflect.*;
import net.sf.cglib.core.*;
import org.apache.commons.collections.*;
import org.apache.commons.lang3.math.*;
import java.math.*;
import java.util.*;

public abstract class Objects
{
    private static final ConcurrentHashMap<Class<?>, BeanCopier> BEAN_COPIERS;
    private static final Set<Class<?>> PRIMITIVES_AND_WRAPPERS;
    private static final Set<Class<?>> IMMUTABLE_CLASSES;
    
    public static final <T> boolean isEquals(final T lhs, final T rhs) {
        return (lhs == null && rhs == null) || (lhs != null && rhs != null && lhs.equals(rhs));
    }
    
    public static boolean isArray(final Object array) {
        return array != null && array.getClass().isArray();
    }
    
    public static boolean isCloneable(final Object obj) {
        if (obj == null) {
            return false;
        }
        final Class<?> clazz = obj.getClass();
        if (!Cloneable.class.isAssignableFrom(obj.getClass())) {
            return false;
        }
        final Method method = Reflections.findPublicMethod(clazz, "clone", new Class[0]);
        return method != null;
    }
    
    public static boolean isPrimitiveOrWrapper(final Object obj) {
        return obj != null && Objects.PRIMITIVES_AND_WRAPPERS.contains(obj.getClass());
    }
    
    public static boolean isArrayOfPrimitives(final Object array) {
        if (array == null) {
            return false;
        }
        final Class<?> clazz = array.getClass();
        return clazz.isArray() && clazz.getComponentType().isPrimitive();
    }
    
    public static boolean isArrayOfPrimitivesOrWrappers(final Object array) {
        if (array == null) {
            return false;
        }
        final Class<?> clazz = array.getClass();
        return clazz.isArray() && Objects.PRIMITIVES_AND_WRAPPERS.contains(clazz);
    }
    
    public static Object getDefaultValue(final Class<?> clazz) {
        if (clazz == null || !clazz.isPrimitive()) {
            return null;
        }
        if (Void.TYPE == clazz) {
            return null;
        }
        if (Boolean.TYPE == clazz) {
            return Boolean.FALSE;
        }
        if (Byte.TYPE == clazz) {
            return 0;
        }
        if (Short.TYPE == clazz) {
            return 0;
        }
        if (Integer.TYPE == clazz) {
            return 0;
        }
        if (Long.TYPE == clazz) {
            return 0L;
        }
        if (Character.TYPE == clazz) {
            return '\0';
        }
        if (Double.TYPE == clazz) {
            return 0.0;
        }
        if (Float.TYPE == clazz) {
            return 0.0f;
        }
        throw new RuntimeException("assertion failed, should not reach here, clazz: " + clazz);
    }
    
    public static Object copy(final Object obj) {
        if (obj == null) {
            return null;
        }
        final Class<?> clazz = obj.getClass();
        if (isPrimitiveOrWrapper(obj)) {
            return obj;
        }
        if (Objects.IMMUTABLE_CLASSES.contains(clazz)) {
            return obj;
        }
        if (isArray(obj)) {
            return arrayCopy(obj);
        }
        if (Set.class.isAssignableFrom(clazz)) {
            return setCopy((Set)obj);
        }
        if (Map.class.isAssignableFrom(clazz)) {
            return mapCopy((Map)obj);
        }
        if (List.class.isAssignableFrom(clazz)) {
            return listCopy((List)obj);
        }
        if (Copyable.class.isAssignableFrom(clazz)) {
            return ((Copyable)obj).copy();
        }
        if (Cloneable.class.isAssignableFrom(clazz) && isCloneable(obj)) {
            return cloneCopy(obj);
        }
        if (Serializable.class.isAssignableFrom(clazz)) {
            return Serializations.copy((Serializable)obj);
        }
        try {
            final Object r = clazz.newInstance();
            copy(obj, r);
            return r;
        }
        catch (Exception e) {
            throw new RuntimeException("failed to copy " + obj, e);
        }
    }
    
    public static Object arrayCopy(final Object array) {
        if (!isArray(array)) {
            throw new IllegalArgumentException("parameter array is not an Array");
        }
        final int length = Array.getLength(array);
        final Object r = Array.newInstance(array.getClass().getComponentType(), length);
        for (int i = 0; i < length; ++i) {
            final Object element = Array.get(array, i);
            if (isArray(element)) {
                Array.set(r, i, arrayCopy(element));
            }
            else {
                Array.set(r, i, copy(element));
            }
        }
        return r;
    }
    
    public static Object setCopy(final Set set) {
        if (set == null) {
            return null;
        }
        try {
            final Set r = (Set)set.getClass().newInstance();
            for (final Object obj : set) {
                r.add(copy(obj));
            }
            return r;
        }
        catch (Exception e) {
            throw new RuntimeException("failed to copy set", e);
        }
    }
    
    public static Object mapCopy(final Map map) {
        if (map == null) {
            return null;
        }
        try {
            final Map r = (Map)map.getClass().newInstance();
            for (final Object key : map.keySet()) {
                r.put(key, copy(map.get(key)));
            }
            return r;
        }
        catch (Exception e) {
            throw new RuntimeException("failed to copy map", e);
        }
    }
    
    public static Object listCopy(final List list) {
        if (list == null) {
            return null;
        }
        try {
            final List r = (List)list.getClass().newInstance();
            for (final Object obj : list) {
                r.add(copy(obj));
            }
            return r;
        }
        catch (Exception e) {
            throw new RuntimeException("failed to copy list", e);
        }
    }
    
    public static Object cloneCopy(final Object obj) {
        if (obj == null) {
            return obj;
        }
        if (!isCloneable(obj)) {
            throw new IllegalArgumentException("parameter obj: " + obj + " is not cloneable");
        }
        try {
            final Class<?> clazz = obj.getClass();
            final Method method = Reflections.findPublicMethod(clazz, "clone", new Class[0]);
            method.setAccessible(true);
            return method.invoke(obj, new Object[0]);
        }
        catch (Exception e) {
            throw new RuntimeException("failed to clone copy", e);
        }
    }
    
    public static void copy(final Object src, final Object dst) {
        if (src == null) {
            throw new IllegalArgumentException("invalid parameter src");
        }
        if (dst == null) {
            throw new IllegalArgumentException("invalid parameter dst");
        }
        if (!src.getClass().equals(dst.getClass())) {
            throw new IllegalArgumentException("the class does not match, src: " + src.getClass() + ", dst: " + dst.getClass());
        }
        final Class<?> clazz = src.getClass();
        final BeanCopier copier = Concurrents.getOrCreate(Objects.BEAN_COPIERS, clazz, new BeanCreator<BeanCopier>() {
            @Override
            public BeanCopier create() {
                return BeanCopier.create(clazz, clazz, false);
            }
        });
        copier.copy(src, dst, (Converter)null);
    }
    
    public static boolean registerImmutableClass(final Class<?> clazz) {
        return Objects.IMMUTABLE_CLASSES.add(clazz);
    }
    
    public static boolean unregisterImmutableClass(final Class<?> clazz) {
        return Objects.IMMUTABLE_CLASSES.remove(clazz);
    }
    
    public static boolean isEmpty(final Object o) {
        if (o == null) {
            return true;
        }
        try {
            return CollectionUtils.sizeIsEmpty(o);
        }
        catch (IllegalArgumentException e) {
            return false;
        }
    }
    
    public static boolean isNumber(final Object o) {
        return o != null && (o instanceof Number || (o instanceof String && NumberUtils.isNumber((String)o)));
    }
    
    public static <T> T parseString(final String s) {
        try {
            return (T)s;
        }
        catch (Throwable t) {
            return null;
        }
    }
    
    public static String toString(final Object o) {
        if (o == null) {
            return "";
        }
        return "" + o;
    }
    
    static {
        BEAN_COPIERS = new ConcurrentHashMap<Class<?>, BeanCopier>();
        (PRIMITIVES_AND_WRAPPERS = new HashSet<Class<?>>()).add(Boolean.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Boolean.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Byte.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Byte.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Character.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Character.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Double.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Double.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Float.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Float.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Integer.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Integer.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Long.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Long.class);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Short.TYPE);
        Objects.PRIMITIVES_AND_WRAPPERS.add(Short.class);
        (IMMUTABLE_CLASSES = new HashSet<Class<?>>()).add(Boolean.class);
        Objects.IMMUTABLE_CLASSES.add(Byte.class);
        Objects.IMMUTABLE_CLASSES.add(Character.class);
        Objects.IMMUTABLE_CLASSES.add(Double.class);
        Objects.IMMUTABLE_CLASSES.add(Float.class);
        Objects.IMMUTABLE_CLASSES.add(Integer.class);
        Objects.IMMUTABLE_CLASSES.add(Long.class);
        Objects.IMMUTABLE_CLASSES.add(Short.class);
        Objects.IMMUTABLE_CLASSES.add(Class.class);
        Objects.IMMUTABLE_CLASSES.add(String.class);
        Objects.IMMUTABLE_CLASSES.add(BigDecimal.class);
        Objects.IMMUTABLE_CLASSES.add(BigInteger.class);
        Objects.IMMUTABLE_CLASSES.add(Date.class);
        Objects.IMMUTABLE_CLASSES.add(java.sql.Date.class);
    }
}
